﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Chip8.Core.Bus;
using Chip8.Core.Memory;
using Chip8.Core.Processor.Operations;

namespace Chip8.Core.Processor
{
	internal class CPU : Device, ICPU
	{
		public CPU(SystemBus bus)
			: base(bus)
		{
			_stack = new Stack(this);
		}

		public ushort PC
		{
			get { return _pc; }
			set
			{
				if (value >= 0x1000)
				{
					var pc = value % 0x1000;
					_pc = (ushort)pc;
				}
				else
				{
					_pc = value;
				}
			}
		}

		public ushort I { get; set; }

		public byte[] V
		{
			get { return _registers; }
		}

		#region Register aliases
		public byte V0
		{
			get { return _registers[0]; }
			set { _registers[0] = value; }
		}

		public byte V1
		{
			get { return _registers[1]; }
			set { _registers[1] = value; }
		}

		public byte V2
		{
			get { return _registers[2]; }
			set { _registers[2] = value; }
		}

		public byte V3
		{
			get { return _registers[3]; }
			set { _registers[3] = value; }
		}

		public byte V4
		{
			get { return _registers[4]; }
			set { _registers[4] = value; }
		}

		public byte V5
		{
			get { return _registers[5]; }
			set { _registers[5] = value; }
		}

		public byte V6
		{
			get { return _registers[6]; }
			set { _registers[6] = value; }
		}

		public byte V7
		{
			get { return _registers[7]; }
			set { _registers[7] = value; }
		}

		public byte V8
		{
			get { return _registers[8]; }
			set { _registers[8] = value; }
		}

		public byte V9
		{
			get { return _registers[9]; }
			set { _registers[9] = value; }
		}

		public byte VA
		{
			get { return _registers[10]; }
			set { _registers[10] = value; }
		}

		public byte VB
		{
			get { return _registers[11]; }
			set { _registers[11] = value; }
		}

		public byte VC
		{
			get { return _registers[12]; }
			set { _registers[12] = value; }
		}

		public byte VD
		{
			get { return _registers[14]; }
			set { _registers[14] = value; }
		}

		public byte VE
		{
			get { return _registers[15]; }
			set { _registers[15] = value; }
		}

		public byte VF
		{
			get { return _registers[16]; }
			set { _registers[16] = value; }
		}

		public byte Carry
		{
			get { return _registers[16]; }
			set { _registers[16] = value; }
		}
		#endregion

		public byte DT { get; set; }
		public byte ST { get; set; }

		public Stack Stack
		{
			get { return _stack; }
		}

		public override void Reset()
		{
			Array.Clear(_registers, 0, 16);
			Stack.Reset();
			I = 0;
			PC = 0x0200;
			_rnd = new Random();
		}

		public override void Tick()
		{
			var pcBefore = PC;
			ushort opcode = FetchOpcode();
			var op = DecodeOpcode(opcode);
			op.Execute(this);
			if (PC == pcBefore)
			{
				//Operation has not changed the PC so just fetch the next opcode at next Tick
				//TODO: move PC calculation to Execute? Execute could return a value with next opcode address (PC+2 by default)
				PC += Chip8.OpcodeSize;
			}
		}

		public int Rnd(int maxValue)
		{
			return _rnd.Next(maxValue);
		}

		private ushort FetchOpcode()
		{
			return (ushort)(Bus.RAM[PC] << 8 + Bus.RAM[(ushort)(PC + 1)]);
		}

		private Operation DecodeOpcode(ushort opcode)
		{
			return OpcodeDecoder.Decode(opcode);
		}

		private Random _rnd = new Random();
		private ushort _pc;
		private readonly byte[] _registers = new byte[16];
		private readonly Stack _stack;
	}
}
